﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Monti.MVVM;

namespace Monti.Docking
{
   /// <summary>
   /// Logic for the main control in the docking library.
   /// </summary>
   public class DockManager : ContentControl, INotifyPropertyChanged
   {
      #region Fields

      /// <summary>
      /// Close dock content command.
      /// </summary>
      private RelayCommand m_CmdClose;

      /// <summary>
      /// Occurs when the active content has changed.
      /// </summary>
      public event EventHandler<DockContentEventArgs> ActiveContentChanged;

      private bool m_IsTopShown;
      private bool m_IsBottomShown;
      private bool m_IsLeftShown;
      private bool m_IsRightShown;
      private Grid m_MainGrid;
      private DockSite m_DocumentDockSite;
      private DockContent m_ActiveDockContent;

      #endregion

      #region Setup

      /// <summary>
      /// Static constructor.
      /// </summary>
      static DockManager ()
      {
         DefaultStyleKeyProperty.OverrideMetadata(typeof(DockManager), 
            new FrameworkPropertyMetadata(typeof(DockManager)));
      }

      #endregion

      #region Properties

      /// <summary>
      /// Gets if the dockmanger as a tool window panel on the left.
      /// </summary>
      public bool HasLeftToolWindow { get { return this.LeftToolWindows != null && this.LeftToolWindows.Items.Count > 0; } }

      /// <summary>
      /// Gets if the dockmanger as a tool window panel on the right.
      /// </summary>
      public bool HasRightToolWindow { get { return this.RightToolWindows != null && this.RightToolWindows.Items.Count > 0; } }

      /// <summary>
      /// Gets if the dockmanger as a tool window panel on the top.
      /// </summary>
      public bool HasTopToolWindow { get { return this.TopToolWindows != null && this.TopToolWindows.Items.Count > 0; } }

      /// <summary>
      /// Gets if the dockmanger as a tool window panel on the bottom.
      /// </summary>
      public bool HasBottomToolWindow { get { return this.BottomToolWindows != null && this.BottomToolWindows.Items.Count > 0; } }

      /// <summary>
      /// Gets the close command.
      /// </summary>
      public ICommand CloseCommand
      {
         get { return this.m_CmdClose ?? (this.m_CmdClose = new RelayCommand (CloseContentExecute)); }
      }

      /// <summary>
      /// Dependency property for dock managers document DockGroup.
      /// </summary>
      public static readonly DependencyProperty DocumentsProperty =
         DependencyProperty.Register ("Documents", typeof (DockGroup), typeof (DockManager),
         new UIPropertyMetadata (null));

      /// <summary>
      /// Gets or Sets the document collection. This is a dependency property.
      /// </summary>
      public DockGroup Documents
      {
         get { return (DockGroup)GetValue (DocumentsProperty); }
         set { SetValue (DocumentsProperty, value); }
      }

      /// <summary>
      /// Dependency property for the collection of left tool windows.
      /// </summary>
      public static readonly DependencyProperty LeftToolWindowsProperty =
         DependencyProperty.Register ("LeftToolWindows", typeof (DockGroup), typeof (DockManager),
         new PropertyMetadata (default (DockGroup), LeftToolWindowGroupChanged));

      /// <summary>
      /// Gets or Sets the collection of left tool windows.
      /// </summary>
      public DockGroup LeftToolWindows
      {
         get { return (DockGroup) GetValue (LeftToolWindowsProperty); }
         set { SetValue (LeftToolWindowsProperty, value); }
      }

      /// <summary>
      /// Dependency property for the collection of right tool windows.
      /// </summary>
      public static readonly DependencyProperty RightToolWindowsProperty =
         DependencyProperty.Register ("RightToolWindows", typeof (DockGroup), typeof (DockManager),
         new PropertyMetadata (default (DockGroup), RightToolWindowGroupChanged));

      /// <summary>
      /// Gets or Sets the collection of right tool windows.
      /// </summary>
      public DockGroup RightToolWindows
      {
         get { return (DockGroup)GetValue (RightToolWindowsProperty); }
         set { SetValue (RightToolWindowsProperty, value); }
      }

      /// <summary>
      /// Dependency property for the collection of top tool windows.
      /// </summary>
      public static readonly DependencyProperty TopToolWindowsProperty =
         DependencyProperty.Register ("TopToolWindows", typeof (DockGroup), typeof (DockManager),
         new PropertyMetadata (default (DockGroup), TopToolWindowGroupChanged));

      /// <summary>
      /// Gets or Sets the collection of top tool windows.
      /// </summary>
      public DockGroup TopToolWindows
      {
         get { return (DockGroup)GetValue (TopToolWindowsProperty); }
         set { SetValue (TopToolWindowsProperty, value); }
      }

      /// <summary>
      /// Dependency property for the collection of bottom tool windows.
      /// </summary>
      public static readonly DependencyProperty BottomToolWindowsProperty =
         DependencyProperty.Register ("BottomToolWindows", typeof (DockGroup), typeof (DockManager),
         new PropertyMetadata (default (DockGroup), BottomToolWindowGroupChanged));

      /// <summary>
      /// Gets or Sets the collection of bottom tool windows.
      /// </summary>
      public DockGroup BottomToolWindows
      {
         get { return (DockGroup)GetValue (BottomToolWindowsProperty); }
         set { SetValue (BottomToolWindowsProperty, value); }
      }

      /// <summary>
      /// Gets or Sets the Active Content. This is a dependency property.
      /// </summary>
      public object ActiveContent
      {
         get { return GetValue (ActiveContentProperty); }
         set { SetValue (ActiveContentProperty, value); }
      }

      /// <summary>
      /// Dependency property for dock managers Active Content.
      /// </summary>
      public static readonly DependencyProperty ActiveContentProperty =
         DependencyProperty.Register ("ActiveContent", typeof (object), typeof (DockManager),
         new UIPropertyMetadata (null, OnActiveContentChange));

      /// <summary>
      /// Dependency property for the custom template selector.
      /// </summary>
      public static readonly DependencyProperty TemplateSelectorProperty =
         DependencyProperty.Register ("TemplateSelector", typeof (CustomTemplateSelector), typeof (DockManager),
         new UIPropertyMetadata (null));

      /// <summary>
      /// Gets or Sets the template selector.
      /// </summary>
      public CustomTemplateSelector TemplateSelector
      {
         get { return (CustomTemplateSelector) GetValue (TemplateSelectorProperty); }
         set { SetValue (TemplateSelectorProperty, value); }
      }

      #endregion

      #region Methods

      /// <summary>
      /// Occurs when the active content changes.
      /// </summary>
      /// <param name="d"></param>
      /// <param name="e"></param>
      private static void OnActiveContentChange (DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
      }

      #region Tool Window Group data

      private static void TopToolWindowGroupChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var dockMan = d as DockManager;
         if (dockMan == null)
            throw new InvalidOperationException ("Unable to determine the dock manager.");

         var group = e.OldValue as DockGroup;
         if (group != null)
            group.Items.CollectionChanged -= dockMan.TopToolWindowItemsChanged;

         group = e.NewValue as DockGroup;
         if (group != null)
            group.Items.CollectionChanged += dockMan.TopToolWindowItemsChanged;
      }

      private void TopToolWindowItemsChanged (object sender, NotifyCollectionChangedEventArgs e)
      {
         if (e.Action == NotifyCollectionChangedAction.Add && !this.m_IsTopShown)
            this.ShowToolWindow (DockArea.Top);
      }

      private static void BottomToolWindowGroupChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var dockMan = d as DockManager;
         if (dockMan == null)
            throw new InvalidOperationException ("Unable to determine the dock manager.");

         var group = e.OldValue as DockGroup;
         if (group != null)
            group.Items.CollectionChanged -= dockMan.BottomToolWindowItemsChanged;

         group = e.NewValue as DockGroup;
         if (group != null)
            group.Items.CollectionChanged += dockMan.BottomToolWindowItemsChanged;
      }

      private void BottomToolWindowItemsChanged (object sender, NotifyCollectionChangedEventArgs e)
      {
         if (e.Action == NotifyCollectionChangedAction.Add && !this.m_IsBottomShown)
            this.ShowToolWindow (DockArea.Bottom);
      }

      private static void LeftToolWindowGroupChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var dockMan = d as DockManager;
         if (dockMan == null)
            throw new InvalidOperationException ("Unable to determine the dock manager.");

         var group = e.OldValue as DockGroup;
         if (group != null)
            group.Items.CollectionChanged -= dockMan.LeftToolWindowItemsChanged;

         group = e.NewValue as DockGroup;
         if (group != null)
            group.Items.CollectionChanged += dockMan.LeftToolWindowItemsChanged;
      }

      private void LeftToolWindowItemsChanged (object sender, NotifyCollectionChangedEventArgs e)
      {
         if (e.Action == NotifyCollectionChangedAction.Add && !this.m_IsLeftShown)
            this.ShowToolWindow (DockArea.Left);
      }

      private static void RightToolWindowGroupChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var dockMan = d as DockManager;
         if (dockMan == null)
            throw new InvalidOperationException ("Unable to determine the dock manager.");

         var group = e.OldValue as DockGroup;
         if (group != null)
            group.Items.CollectionChanged -= dockMan.RightToolWindowItemsChanged;

         group = e.NewValue as DockGroup;
         if (group != null)
            group.Items.CollectionChanged += dockMan.RightToolWindowItemsChanged;
      }

      private void RightToolWindowItemsChanged (object sender, NotifyCollectionChangedEventArgs e)
      {
         if (e.Action == NotifyCollectionChangedAction.Add && !this.m_IsRightShown)
            this.ShowToolWindow (DockArea.Right);
      }

      #endregion

      /// <summary>
      /// Fires the active content changed handler.
      /// </summary>
      /// <param name="content">Content that caused the change.</param>
      internal void FireActiveContentChanged (DockContent content)
      {
         if (this.m_ActiveDockContent != null)
         {
            this.m_ActiveDockContent.DockSite.HasActiveContent = false;
            this.m_ActiveDockContent.IsActiveContent = false;
         }
         this.m_ActiveDockContent = content;
         this.m_ActiveDockContent.DockSite.HasActiveContent = true;
         this.ActiveContent = content.DataContext;
         if (this.ActiveContent != null)
         {
            var dockItem = this.ActiveContent as IDockItem;
            if (dockItem != null && !dockItem.IsActiveContent)
               dockItem.IsActiveContent = true;

            if (this.ActiveContentChanged != null)
               this.ActiveContentChanged (content.DockSite.Group, new DockContentEventArgs (content.DataContext));
         }
      }

      /// <summary>
      /// Occurs when the close command is executed.
      /// </summary>
      /// <param name="param">DockContent that is requesting to close.</param>
      private static void CloseContentExecute (object param)
      {
         var content = param as DockContent;
         if (content == null)
         {
            var site = param as DockSite;
            if (site != null)
               site.RemoveContent (site.SelectedValue);
            return;
         }
         content.DockSite.RemoveContent (content.DataContext);
      }

      /// <summary>
      /// When overridden in a derived class, is invoked whenever
      /// application code or internal processes call <see cref="M:System.Windows.FrameworkElement.ApplyTemplate"/>.
      /// </summary>
      public override void OnApplyTemplate ()
      {
         this.TemplateSelector = new CustomTemplateSelector ();
         this.m_DocumentDockSite = (DockSite)this.Template.FindName ("PART_DocumentDockSite", this);
         this.m_DocumentDockSite.DockMan = this;
         this.m_DocumentDockSite.Group = new DockGroup ();
         this.Documents = this.m_DocumentDockSite.Group;

         this.m_MainGrid = (Grid)this.Template.FindName ("PART_MainGrid", this);
         base.OnApplyTemplate ();
      }

      /// <summary>
      /// Adds a item to a tool window.
      /// </summary>
      /// <param name="dockItem">Item to add.</param>
      /// <param name="dockArea">Area tool window is in.</param>
      internal void AddToolWindow (object dockItem, DockArea dockArea)
      {
         if (dockArea == DockArea.Top)
         {
            if (this.TopToolWindows == null)
               this.TopToolWindows = new DockGroup ();
            this.TopToolWindows.Items.Add (dockItem);
         }
         else if (dockArea == DockArea.Bottom)
         {
            if (this.BottomToolWindows == null)
               this.BottomToolWindows = new DockGroup ();
            this.BottomToolWindows.Items.Add (dockItem);
         }
         else if (dockArea == DockArea.Left)
         {
            if (this.LeftToolWindows == null)
               this.LeftToolWindows = new DockGroup ();
            this.LeftToolWindows.Items.Add (dockItem);
         }
         else if (dockArea == DockArea.Right)
         {
            if (this.RightToolWindows == null)
               this.RightToolWindows = new DockGroup ();
            this.RightToolWindows.Items.Add (dockItem);
         }
      }

      /// <summary>
      /// Shows the tool window for the given dock area.
      /// </summary>
      /// <param name="dockArea">Area of tool window.</param>
      private void ShowToolWindow (DockArea dockArea)
      {
         var dockSite = new DockSite { IsToolWindow = true };

         if (dockArea == DockArea.Top)
         {
            this.m_IsTopShown = true;
            dockSite.Group = this.TopToolWindows;
         }
         else if (dockArea == DockArea.Bottom)
         {
            this.m_IsBottomShown = true;
            dockSite.Group = this.BottomToolWindows;
         }
         else if (dockArea == DockArea.Left)
         {
            this.m_IsLeftShown = true;
            dockSite.Group = this.LeftToolWindows;
         }
         else if (dockArea == DockArea.Right)
         {
            this.m_IsRightShown = true;
            dockSite.Group = this.RightToolWindows;
         }
         this.m_DocumentDockSite.AddNewDockSite (dockArea, dockSite, m_DocumentDockSite.GetParentSplitPanel ());
      }

      #endregion

      #region Property Changed

      /// <summary>
      /// Event used to notify when a property has changed.
      /// </summary>
      public event PropertyChangedEventHandler PropertyChanged;

      /// <summary>
      /// Fires the property changed event.
      /// </summary>
      /// <param name="propertyName">Name of property that changed.</param>
      protected void FirePropertyChange (string propertyName)
      {
         if (this.PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }

      #endregion
   }
}
